package org.mylitespring.beans.factory.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.mylitespring.beans.BeanDefinition;
import org.mylitespring.beans.PropertyValue;
import org.mylitespring.beans.factory.BeanDefinitionStoreException;
import org.mylitespring.beans.factory.config.RuntimeBeanReference;
import org.mylitespring.beans.factory.config.TypedStringValue;
import org.mylitespring.beans.factory.support.BeanDefinitionRegistry;
import org.mylitespring.beans.factory.support.GenericBeanDefinition;
import org.mylitespring.core.io.Resource;
import org.mylitespring.util.StringUtils;

public class XmlBeanDefinitionReader {

	public static final String ID_ATTRIBUTE="id";
	public static final String CLASS_ATTRIBUTE="class";
	public static final String SCOPE_ATTRIBUTE="scope";
	
	public static final String PROPERTY_ELEMENT="property";
	public static final String REF_ATTRIBUTE="ref";
	public static final String VALUE_ATTRIBUTE="value";
	public static final String NAME_ATTRIBUTE="name";
	
	BeanDefinitionRegistry registry;
	
	protected final Log logger = LogFactory.getLog(getClass());
	
	public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
		this.registry=registry;
	}
	
	public void loadBeanDefinitions(Resource resource) {
		InputStream is=null;
		try {
			is=resource.getInputStream();
			SAXReader reader=new SAXReader();
			Document doc =reader.read(is);
			
			Element root=doc.getRootElement();//<beans>
			Iterator<Element> iterator=root.elementIterator();
			while(iterator.hasNext()) {
				Element ele=(Element)iterator.next();
				String id=ele.attributeValue(ID_ATTRIBUTE);
				String beanClassName=ele.attributeValue(CLASS_ATTRIBUTE);
				BeanDefinition bd=new GenericBeanDefinition(id,beanClassName);
				if (ele.attribute(SCOPE_ATTRIBUTE)!=null) {
					bd.setScope(ele.attributeValue(SCOPE_ATTRIBUTE));
				}
				parsePropertyElement(ele,bd); 
				this.registry.registerBeanDefinition(id,bd);
			}
		} catch (Exception e) {
			throw new BeanDefinitionStoreException("IOException parsing XML document from "+resource.getDescription(),e);
		} finally {
			if (is!=null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void parsePropertyElement(Element ele, BeanDefinition bd) {
		Iterator iterator=ele.elementIterator(PROPERTY_ELEMENT);
		while(iterator.hasNext()) {
			Element propElem=(Element)iterator.next();
			String propertyName=propElem.attributeValue(NAME_ATTRIBUTE);
			if (!StringUtils.hasLength(propertyName)) {
				logger.fatal("Tag 'property' must have a 'name' attribute");
				return;
			}
			
			Object val=parsePropertyElement(propElem, bd,propertyName);
			
			PropertyValue pv=new PropertyValue(propertyName, val);
			bd.getPropertyValues().add(pv);
		}
	}

	private Object parsePropertyElement(Element propElem, BeanDefinition bd, String propertyName) {
		String elementName=(propertyName!=null)?"<property> element for property '" + propertyName + "'":"<constructor-arg> element";
		
		boolean hasRefAttribute=(propElem.attribute(REF_ATTRIBUTE)!=null);
		boolean hasValueAttribute=(propElem.attribute(VALUE_ATTRIBUTE)!=null);
		
		if (hasRefAttribute) {
			String refName=propElem.attributeValue(REF_ATTRIBUTE);
			if (!StringUtils.hasText(refName)) {
				logger.error(elementName+" contains empty 'ref' attribute");
			}
			RuntimeBeanReference reference=new RuntimeBeanReference(refName);
			return reference;
		}else if(hasValueAttribute) {
			TypedStringValue valueHolder=new TypedStringValue(propElem.attributeValue(VALUE_ATTRIBUTE));
			return valueHolder;
		}else {
			throw new RuntimeException(elementName + " must specify a ref or value");
		}
	}
}











